// SPDX-License-Identifier: MIT

#pragma once

#include "ArchHelpers/UContext.h"
#include "CodeLoader.h"
#include "Common/FDUtils.h"
#include "FEXCore/Utils/Allocator.h"
#include "LinuxSyscalls/Syscalls.h"
#include "VDSO_Emulation.h"
#include "Linux/Utils/ELFParser.h"

#include <cstring>

#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Utils/MathUtils.h>
#include <FEXCore/Core/X86Enums.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/TypeDefines.h>
#include <FEXCore/Utils/FileLoading.h>
#include <FEXCore/fextl/list.h>
#include <FEXCore/fextl/string.h>
#include <FEXCore/fextl/vector.h>
#include <FEXHeaderUtils/Syscalls.h>
#include <FEXHeaderUtils/SymlinkChecks.h>

#include <elf.h>
#include <fcntl.h>
#include <fmt/format.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/random.h>
#include <linux/prctl.h>

#define PAGE_START(x) ((x) & ~(uintptr_t)(4095))
#define PAGE_OFFSET(x) ((x) & 4095)
#define PAGE_ALIGN(x) (((x) + 4095) & ~(uintptr_t)(4095))

class ELFCodeLoader final : public FEX::CodeLoader {
  ELFParser MainElf {};
  ELFParser InterpElf {};

  bool ElfValid {false};
  bool ExecutableStack {false};
  uintptr_t MainElfBase {};
  uintptr_t InterpeterElfBase {};
  uintptr_t MainElfEntrypoint {};
  uintptr_t Entrypoint {};
  uintptr_t BrkStart {};
  uintptr_t StackPointer {};

  // This calculates the map size for ET_DYN type ELF files.
  // Can't be used for ET_EXEC ELF files because they can have large virtual mapping holes.
  static size_t CalculateDYNELFSize(const fextl::vector<Elf64_Phdr>& headers) {
    bool had_pt_load = false;
    size_t min_map_address = ~0ULL;
    size_t max_map_address = 0;
    for (const auto& it : headers) {
      if (it.p_type != PT_LOAD) {
        // Skip everything but PT_LOAD.
        continue;
      }

      had_pt_load = true;
      min_map_address = std::min(min_map_address, PAGE_START(it.p_vaddr));
      max_map_address = std::max(max_map_address, it.p_vaddr + it.p_memsz);
    }

    if (!had_pt_load) {
      // No load segments, so need to be safe and return zero.
      return 0;
    }

    return FEXCore::AlignUp(max_map_address - min_map_address, FEXCore::Utils::FEX_PAGE_SIZE);
  }

  bool MapFile(const ELFParser& file, uintptr_t Base, const Elf64_Phdr& Header, int prot, int flags, FEX::HLE::SyscallMmapInterface* const Handler) {

    auto addr = Base + PAGE_START(Header.p_vaddr);
    auto size = Header.p_filesz + PAGE_OFFSET(Header.p_vaddr);
    auto off = Header.p_offset - PAGE_OFFSET(Header.p_vaddr);

    size = PAGE_ALIGN(size);
    if (size == 0) {
      // PT_LOAD section without a file size
      // Will need to have a memory size that is not zero instead
      return true;
    }

    void* rv = Handler->GuestMmap(nullptr, (void*)addr, size, prot, flags, file.fd, off);

    if (FEX::HLE::HasSyscallError(rv)) {
      // uhoh, something went wrong
      LogMan::Msg::EFmt("MapFile: Some elf mapping failed, {}, fd: {}\n", errno, file.fd);
      return false;
    } else {
      char Tmp[PATH_MAX];
      auto PathLength = FEX::get_fdpath(file.fd, Tmp);
      if (PathLength != -1) {
        Sections.push_back({Base, (uintptr_t)rv, size, (off_t)off, fextl::string(Tmp, PathLength), (prot & PROT_EXEC) != 0});
      }

      return true;
    }
  }

  int MapFlags(const Elf64_Phdr& Header) {
    int rv = 0;

    if (Header.p_flags & PF_R) {
      rv |= PROT_READ;
    }

    if (Header.p_flags & PF_W) {
      rv |= PROT_WRITE;
    }

    if (Header.p_flags & PF_X) {
      rv |= PROT_EXEC;
    }

    return rv;
  }

  std::optional<uintptr_t> LoadElfFile(ELFParser& Elf, uintptr_t* BrkBase, FEX::HLE::SyscallMmapInterface* const Handler, uint64_t LoadHint = 0) {

    uintptr_t LoadBase = 0;
    uintptr_t BrkLoadBase = 0;
    const bool DynELF = Elf.ehdr.e_type == ET_DYN;
    const bool NeedsLateBRKMap = BrkBase && !DynELF;

    if (DynELF) {
      // Allocate a base address plus BRK padding.
      auto TotalSize = CalculateDYNELFSize(Elf.phdrs) + (BrkBase ? BRK_SIZE : 0);
      LoadBase =
        (uintptr_t)Handler->GuestMmap(nullptr, reinterpret_cast<void*>(LoadHint), TotalSize, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
      if (FEX::HLE::HasSyscallError(LoadBase)) {
        return {};
      }

      // fprintf(stderr, "elf %d: %lx-%lx\n", Elf.fd, LoadBase, LoadBase + TotalSize);
      if (BrkBase) {
        BrkLoadBase = LoadBase + (TotalSize - BRK_SIZE);
      }
    }

    for (const auto& Header : Elf.phdrs) {
      if (Header.p_type != PT_LOAD) {
        continue;
      }

      int MapProt = MapFlags(Header);
      int MapType = MAP_PRIVATE | MAP_DENYWRITE | MAP_FIXED;

      if (!MapFile(Elf, LoadBase, Header, MapProt, MapType, Handler)) {
        return {};
      }

      if (Header.p_memsz > Header.p_filesz) {
        // clear bss
        auto BSSStart = LoadBase + Header.p_vaddr + Header.p_filesz;
        auto BSSPageStart = PAGE_ALIGN(BSSStart);
        auto BSSPageEnd = PAGE_ALIGN(LoadBase + Header.p_vaddr + Header.p_memsz);

        // Only clear padding bytes if the section is writable
        if (Header.p_flags & PF_W) {
          memset((void*)BSSStart, 0, BSSPageStart - BSSStart);
        }

        if (BSSPageStart != BSSPageEnd) {
          auto bss = Handler->GuestMmap(nullptr, (void*)BSSPageStart, BSSPageEnd - BSSPageStart, MapProt, MapType | MAP_ANONYMOUS, -1, 0);
          if (FEX::HLE::HasSyscallError(bss)) {
            LogMan::Msg::EFmt("Failed to allocate BSS @ {}, {}\n", fmt::ptr(bss), errno);
            return {};
          }
        }
      }

      if (NeedsLateBRKMap) {
        // Keep track of highest address for BRK in the case of non-dynamic ELF files.
        auto memend = LoadBase + Header.p_vaddr + Header.p_memsz;

        // track elf_brk
        if (memend > BrkLoadBase) {
          BrkLoadBase = FEXCore::AlignUp(memend, FEXCore::Utils::FEX_PAGE_SIZE);
        }
      }
    }

    if (NeedsLateBRKMap) {
      // Map the BRK after ELF if possible.
      BrkLoadBase =
        (uintptr_t)Handler->GuestMmap(nullptr, reinterpret_cast<void*>(BrkLoadBase), BRK_SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
      if (FEX::HLE::HasSyscallError(BrkLoadBase)) {
        // This isn't a catastrophic failure. This just means the BRK conflicted with the ELF.
        BrkLoadBase = 0;
      }
    }

    if (BrkBase) {
      *BrkBase = BrkLoadBase;
    }

    return LoadBase;
  }

  static bool GetRandom(void* Data, size_t DataSize) {
    ssize_t Result {};
    do {
      // This is guaranteed to not be interrupted by a signal,
      // since fewer than 256 bytes of RNG data are requested
      Result = getrandom(Data, DataSize, 0);
    } while (Result != -1 && Result != DataSize);

    return Result != -1;
  }

public:

  static fextl::string ResolveRootfsFile(const fextl::string& File, fextl::string RootFS) {
    // If the path is relative then just run that
    if (File[0] != '/') {
      return File;
    }

    fextl::string RootFSLink = RootFS + File;

    char Filename[PATH_MAX];
    while (FHU::Symlinks::IsSymlink(RootFSLink.c_str())) {
      // Do some special handling if the RootFS's linker is a symlink
      // Ubuntu's rootFS by default provides an absolute location symlink to the linker
      // Resolve this around back to the rootfs
      auto SymlinkPath = FHU::Symlinks::ResolveSymlink(RootFSLink.c_str(), Filename);
      if (SymlinkPath.starts_with('/')) {
        RootFSLink = RootFS;
        RootFSLink += SymlinkPath;
      } else {
        break;
      }
    }

    return RootFSLink;
  }

  struct LoadedSection {
    uintptr_t ElfBase;
    uintptr_t Base;
    size_t Size;
    off_t Offs;
    fextl::string Filename;
    bool Executable;
  };

  fextl::vector<LoadedSection> Sections;

  ELFCodeLoader(const fextl::string& Filename, int ProgramFDFromEnv, const fextl::string& RootFS, const fextl::vector<fextl::string>& args,
                const fextl::vector<fextl::string>& ParsedArgs, char** const envp = nullptr,
                FEXCore::Config::Value<FEXCore::Config::StringArrayType>* AdditionalEnvp = nullptr, bool SkipInterpreter = false) {
    ApplicationArgs = args;

    bool LoadedWithFD = false;
    int FD = getauxval(AT_EXECFD);

    if (ProgramFDFromEnv != -1) {
      // If we passed the execve FD to us then use that.
      FD = ProgramFDFromEnv;
    }

    // If we are provided an EXECFD then attempt to execute that first
    // This happens in the case of binfmt_misc usage
    if (FD != 0) {
      if (!MainElf.ReadElf(FD)) {
        return;
      }
      LoadedWithFD = true;
    } else {
      if (!MainElf.ReadElf(ResolveRootfsFile(Filename, RootFS)) && !MainElf.ReadElf(Filename)) {
        return;
      }
    }

    // If we have loaded with EXECFD then we have binfmt_misc preserve argv[0] also set
    // This adds an additional argument to our argument list that we need to ignore
    // argv[0] = FEX
    // argv[1] = <Path to binary>
    // argv[2] = <original user typed path to binary>
    // If our kernel if v5.12 or higher then
    // We can check if this exists by checking auxv[AT_FLAGS] for AT_FLAGS_PRESERVE_ARGV0
    // Else we need to make an assumption that if we were loaded with FD that we have preserve enabled

    uint64_t AtFlags = getauxval(AT_FLAGS);
#ifndef AT_FLAGS_PRESERVE_ARGV0
#define AT_FLAGS_PRESERVE_ARGV0 1
#endif
    uint32_t HostKernel = FEX::HLE::SyscallHandler::CalculateHostKernelVersion();
    if ((HostKernel >= FEX::HLE::SyscallHandler::KernelVersion(5, 12, 0) && (AtFlags & AT_FLAGS_PRESERVE_ARGV0)) || LoadedWithFD) {

      // Erase the initial argument from the list in this case
      ApplicationArgs.erase(ApplicationArgs.begin());
    }

    // Append any additional arguments from config
    const auto AdditionalArgs = AdditionalArguments.All();
    ApplicationArgs.insert(ApplicationArgs.end(), AdditionalArgs.begin(), AdditionalArgs.end());

    if (!MainElf.InterpreterElf.empty() && !SkipInterpreter) {
      if (!InterpElf.ReadElf(ResolveRootfsFile(MainElf.InterpreterElf, RootFS)) && !InterpElf.ReadElf(MainElf.InterpreterElf)) {
        return;
      }

      if (!InterpElf.InterpreterElf.empty()) {
        return;
      }

      if (InterpElf.type != MainElf.type) {
        return;
      }
    }

    ElfValid = true;

    if (envp) {
      // If we had envp passed in then make sure to set it up on the guest
      for (size_t i = 0;; ++i) {
        if (envp[i] == nullptr) {
          break;
        }
        EnvironmentVariables.emplace_back(envp[i]);
      }
    }

    if (AdditionalEnvp) {
      const auto EnvpList = AdditionalEnvp->All();
      EnvironmentVariables.insert(EnvironmentVariables.end(), EnvpList.begin(), EnvpList.end());
    }

    if (InjectLibSegFault()) {
      EnvironmentVariables.emplace_back("LD_PRELOAD=libSegFault.so");
    }

    // Calculate argument and envp backing sizes
    for (const auto& Arg : ApplicationArgs) {
      ArgumentBackingSize += Arg.size() + 1;
    }
    for (const auto& EnvVar : EnvironmentVariables) {
      EnvironmentBackingSize += EnvVar.size() + 1;
    }

    for (const auto& Arg : ParsedArgs) {
      LoaderArgs.emplace_back(Arg.c_str());
    }
  }

  void FreeSections() {
    Sections.clear();
  }

  uint64_t StackSize() const override {
    return STACK_SIZE;
  }
  uint64_t GetStackPointer() const override {
    return StackPointer;
  }
  uint64_t DefaultRIP() const override {
    return Entrypoint;
  }

  struct auxv32_t {
    uint32_t key;
    uint32_t val;
  };

  struct auxv_t {
    uint64_t key;
    uint64_t val;
  };

  std::optional<uintptr_t> LoadMainElfFile(uintptr_t* BrkBase, FEX::HLE::SyscallMmapInterface* const Handler, uint64_t LoadHint = 0) {
    return LoadElfFile(MainElf, BrkBase, Handler, LoadHint);
  }

  bool MapMemory(FEX::HLE::SyscallMmapInterface* const Handler) {
    for (const auto& Header : MainElf.phdrs) {
      if (Header.p_type == PT_GNU_STACK) {
        if (Header.p_flags & PF_X) {
          ExecutableStack = true;
        }
      }

      // We ignore LOPROC..HIPROC here, kernel has a platform specific hook about it
      // Both for the main and the interpreter elf
    }

    // Set the process personality here
    // This needs some more investigation
    // READ_IMPLIES_EXEC might be default for 32-bit elfs
    // Also, what about ADDR_LIMIT_3GB & co ?
    if (-1 == personality(PER_LINUX | (ExecutableStack ? READ_IMPLIES_EXEC : 0))) {
      LogMan::Msg::EFmt("Setting personality failed");
      return false;
    }

    // What about ASLR and such ?
    // ADDR_LIMIT_3GB STACK -> 0xc0000000 else -> 0xFFFFe000

    // map stack here, so that nothing gets mapped there
    // This works with both 64-bit and 32-bit. The mapper will only give us a function in the correct region
    //
    // MAP_GROWSDOWN is required here. The default stack pointer allocated by the kernel is mapped with it.
    // Some libraries (like libfmod) will have a PT_GNU_STACK with executable stack bit set
    // On dlopen glibc will check its current stack allocation permission bits (using internal expectations of allocation, not
    // /proc/self/maps) If stack hasn't been allocated as executable then it will proceed to mprotect the range with the executable bit set
    // Then it will mprotect the base stack page with `PROT_READ|PROT_WRITE|PROT_EXEC|PROT_GROWSDOWN`
    // If the original stack memory region wasn't allocated with MAP_GROWSDOWN then the mprotect with PROT_GROWSDOWN will fail with EINVAL
    //
    // This is still technically a memory leak if the stack grows, but since the primary thread's stack only gets destroyed on process
    // close, this is fine.

    // Stacks need to be allocated at the hint location just like on a real x86 system.
    // These are 128MB regions on both x86-64 and x86.
    //
    // These are required to be in the correct location taking up the appropriate 128MB of space, otherwise the wine preloader crashes FEX.
    // This is due to the wine-preloader hardcoding addresses [0x7FFFFE000000 - 0x7FFFFFFF0000) as a top-down
    // allocation region. They use mmap with MAP_FIXED, ignoring any previously mapped area at that location and overwriting it.
    // Wine-preloader is expecting to allocate 32MB out of the total 128MB stack space in this case. Leaving 96MB for the application.
    //
    // If FEX doesn't allocate the stack in this region (nullptr mmap hint) then later allocations that FEX does will /eventually/
    // end up inside of this address space that wine allocates. This usually ends up being a JIT CodeBuffer, which zeroes the memory and
    // faults with a SIGILL.
    //
    // On the upside, this more accurately emulates how the kernel allocates stack space for the application when hinting at the location.
    //
    void* StackPointerBase {};
    uint64_t StackHint = Is64BitMode() ? STACK_HINT_64 : STACK_HINT_32;

    // Allocate the base of the full 128MB stack range.
    StackPointerBase = Handler->GuestMmap(nullptr, reinterpret_cast<void*>(StackHint), FULL_STACK_SIZE, PROT_NONE,
                                          MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN | MAP_NORESERVE, -1, 0);


    if (FEX::HLE::HasSyscallError(StackPointerBase)) {
      LogMan::Msg::EFmt("Allocating stack failed");
      return false;
    }

    // Allocate with permissions the 8MB of regular stack size.
    StackPointer = reinterpret_cast<uintptr_t>(
      Handler->GuestMmap(nullptr, reinterpret_cast<void*>(reinterpret_cast<uint64_t>(StackPointerBase) + FULL_STACK_SIZE - StackSize()),
                         StackSize(), PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_GROWSDOWN, -1, 0));

    if (FEX::HLE::HasSyscallError(StackPointer)) {
      LogMan::Msg::EFmt("Allocating stack failed");
      return false;
    }

    // Load the interpreter ELF first.
    // This allows the top-down allocation of the kernel to put this at the top of the VA space.
    // This matches behaviour of native execution more closely.
    //
    // eg:
    // 555555554000-555555558000 r--p 00000000 103:0a 1311400                   /usr/bin/ls
    // 555555558000-55555556c000 r-xp 00004000 103:0a 1311400                   /usr/bin/ls
    // 55555556c000-555555574000 r--p 00018000 103:0a 1311400                   /usr/bin/ls
    // 555555575000-555555577000 rw-p 00020000 103:0a 1311400                   /usr/bin/ls
    // 555555577000-555555578000 rw-p 00000000 00:00 0                          [heap]
    // 7ffff7fbb000-7ffff7fbd000 rw-p 00000000 00:00 0
    // 7ffff7fbd000-7ffff7fc1000 r--p 00000000 00:00 0                          [vvar]
    // 7ffff7fc1000-7ffff7fc3000 r-xp 00000000 00:00 0                          [vdso]
    // 7ffff7fc3000-7ffff7fc5000 r--p 00000000 103:0a 1316948                   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    // 7ffff7fc5000-7ffff7fef000 r-xp 00002000 103:0a 1316948                   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    // 7ffff7fef000-7ffff7ffa000 r--p 0002c000 103:0a 1316948                   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    // 7ffff7ffb000-7ffff7fff000 rw-p 00037000 103:0a 1316948                   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    // 7ffffffdd000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
    // ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
    //
    // ARM:
    // 55ccaf8b1000-55ccaf8b5000 r--p 00000000 00:2a 4                          /tmp/.FEXMount178532-oiFrTF/usr/bin/ls
    // 55ccaf8b5000-55ccaf8c9000 r-xp 00004000 00:2a 4                          /tmp/.FEXMount178532-oiFrTF/usr/bin/ls
    // 55ccaf8c9000-55ccaf8d1000 r--p 00018000 00:2a 4                          /tmp/.FEXMount178532-oiFrTF/usr/bin/ls
    // 55ccaf8d1000-55ccaf8d2000 ---p 00000000 00:00 0
    // 55ccaf8d2000-55ccaf8d4000 rw-p 00020000 00:2a 4                          /tmp/.FEXMount178532-oiFrTF/usr/bin/ls
    // 55ccaf8d4000-55ccb00d5000 rw-p 00000000 00:00 0
    // <... Snip of misc allocations ...>
    // 7fffff6c2000-7fffff6c4000 r--p 00000000 00:2a 22 /tmp/.FEXMount178532-oiFrTF/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffff6c4000-7fffff6ee000
    // r-xp 00002000 00:2a 22                         /tmp/.FEXMount178532-oiFrTF/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffff6ee000-7fffff6f9000
    // r--p 0002c000 00:2a 22                         /tmp/.FEXMount178532-oiFrTF/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffff6f9000-7fffff6fa000
    // ---p 00000000 00:00 0 7fffff6fa000-7fffff6fe000 rw-p 00037000 00:2a 22
    // /tmp/.FEXMount178532-oiFrTF/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffff7fe000-7fffffffe000 rw-p 00000000 00:00 0 7fffffffe000-7ffffffff000
    // r--p 00000000 08:82 7082611                    /usr/share/fex-emu/GuestThunks/libVDSO-guest.so 7ffffffff000-800000000000 rw-p
    // 00000000 00:00 0
    uint64_t ELFLoadHint = 0;

    if (!MainElf.InterpreterElf.empty()) {
      uint64_t InterpLoadBase = 0;
      if (auto elf = LoadElfFile(InterpElf, nullptr, Handler)) {
        InterpLoadBase = *elf;
      } else {
        LogMan::Msg::EFmt("Failed to load interpreter elf file");
        return false;
      }

      InterpeterElfBase = InterpLoadBase + InterpElf.phdrs.front().p_vaddr - InterpElf.phdrs.front().p_offset;
      Entrypoint = InterpLoadBase + InterpElf.ehdr.e_entry;

      // If the ELF has an interpreter and is dynamic then we should provide a address hint for loading.
      // The kernel calculates this `load_bias` by dividing the task size by three then multiplying by two.
      // It then also offsets by a random number for ASLR purposes.
      //
      // Random number that gets added to the base needs to be in the number of bits (multiplied by pages):
      // 64-bit: [28, 32] bits
      // 32-bit: [8, 16] bits
      // By default the /minimum/ number of bits is used here.
      constexpr uint64_t TASK_SIZE_64 = (1ULL << 47);
      constexpr uint64_t TASK_SIZE_32 = (1ULL << 32);
      if (Is64BitMode()) {
        // Ensure that if we are running on a 36-bit VA system, we don't try hinting that an ELF should
        // live way outside the VA space.
        uint64_t HostVASize = 1ULL << FEXCore::Allocator::DetermineVASize();
        ELFLoadHint = std::min(HostVASize, TASK_SIZE_64) / 3 * 2;
      } else {
        ELFLoadHint = TASK_SIZE_32 / 3 * 2;
      }
#define ASLR_LOAD
#ifdef ASLR_LOAD
      // Only enable ASLR randomization if the personality has it enabled.
      uint32_t Personality = personality(~0ULL);
      bool NoRandomize = (Personality & ADDR_NO_RANDOMIZE) == ADDR_NO_RANDOMIZE;

      if (!NoRandomize) {
        constexpr uint64_t ASLR_BITS_64 = 28;
        constexpr uint64_t ASLR_BITS_32 = 8;
        uint64_t ASLR_Offset {};
        if (!GetRandom(&ASLR_Offset, sizeof(ASLR_Offset))) {
          // getrandom failed for some reason.
          ASLR_Offset = 0;
          LogMan::Msg::EFmt("RNG failed. ASLR will not work.");
        }

        if (Is64BitMode()) {
          ASLR_Offset &= (1ULL << ASLR_BITS_64) - 1;
        } else {
          ASLR_Offset &= (1ULL << ASLR_BITS_32) - 1;
        }

        ASLR_Offset <<= FEXCore::Utils::FEX_PAGE_SHIFT;
        ELFLoadHint += ASLR_Offset;
      }
#endif
      // Align the mapping
      ELFLoadHint &= FEXCore::Utils::FEX_PAGE_MASK;
    }

    // load the main elf

    uintptr_t LoadBase = 0;

    if (auto elf = LoadElfFile(MainElf, &BrkStart, Handler, ELFLoadHint)) {
      LoadBase = *elf;
      if (MainElf.ehdr.e_type == ET_DYN) {
        BaseOffset = LoadBase;
      }
    } else {
      LogMan::Msg::EFmt("Failed to load elf file");
      return false;
    }

    if (BrkStart) {
      // BRK usually comes directly after where the ELF is loaded.
      // If a value was returned then we have mapped the entire `BRK_SIZE` and need to change protections.
      BrkStart = (uint64_t)Handler->GuestMmap(nullptr, (void*)BrkStart, BRK_SIZE, PROT_READ | PROT_WRITE,
                                              MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
      if (FEX::HLE::HasSyscallError(BrkStart)) {
        LogMan::Msg::EFmt("Failed to allocate BRK @ {:x}, {}\n", BrkStart, errno);
        return false;
      }
    }

    MainElfBase = LoadBase + MainElf.phdrs.front().p_vaddr - MainElf.phdrs.front().p_offset;
    MainElfEntrypoint = LoadBase + MainElf.ehdr.e_entry;

    if (MainElf.InterpreterElf.empty()) {
      InterpeterElfBase = 0;
      Entrypoint = MainElfEntrypoint;
    }

    // All done

    // Setup AuxVars
    AuxVariables.emplace_back(auxv_t {11, getauxval(AT_UID)});            // AT_UID
    AuxVariables.emplace_back(auxv_t {12, getauxval(AT_EUID)});           // AT_EUID
    AuxVariables.emplace_back(auxv_t {13, getauxval(AT_GID)});            // AT_GID
    AuxVariables.emplace_back(auxv_t {14, getauxval(AT_EGID)});           // AT_EGID
    AuxVariables.emplace_back(auxv_t {17, getauxval(AT_CLKTCK)});         // AT_CLKTIK
    AuxVariables.emplace_back(auxv_t {6, FEXCore::Utils::FEX_PAGE_SIZE}); // AT_PAGESIZE
    AuxRandom = &AuxVariables.emplace_back(auxv_t {25, ~0ULL});           // AT_RANDOM
    AuxVariables.emplace_back(auxv_t {23, getauxval(AT_SECURE)});         // AT_SECURE
    AuxVariables.emplace_back(auxv_t {8, 0});                             // AT_FLAGS
    AuxVariables.emplace_back(auxv_t {5, MainElf.phdrs.size()});          // AT_PHNUM
    AuxVariables.emplace_back(auxv_t {16, HWCap});                        // AT_HWCAP
    AuxVariables.emplace_back(auxv_t {26, HWCap2});                       // AT_HWCAP2
    AuxVariables.emplace_back(auxv_t {51, CalculateSignalStackSize()});   // AT_MINSIGSTKSZ
    AuxPlatform = &AuxVariables.emplace_back(auxv_t {24, ~0ULL});         // AT_PLATFORM
    AuxExecFN = &AuxVariables.emplace_back(auxv_t {AT_EXECFN, ~0ULL});    // AT_EXECFN

    if (Is64BitMode()) {
      AuxVariables.emplace_back(auxv_t {4, 0x38}); // AT_PHENT
    } else {
      AuxVariables.emplace_back(auxv_t {4, 0x20}); // AT_PHENT

      auto VSyscallEntry = FEX::VDSO::GetVSyscallEntry(VDSOBase);
      if (!VSyscallEntry) [[unlikely]] {
        // If the VDSO thunk doesn't exist then we might not have a vsyscall entry.
        // Newer glibc requires vsyscall to exist now. So let's allocate a buffer and stick a vsyscall in to it.
        auto VSyscallPage =
          Handler->GuestMmap(nullptr, nullptr, FEXCore::Utils::FEX_PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
        constexpr static uint8_t VSyscallCode[] = {
          0xcd, 0x80, // int 0x80
          0xc3,       // ret
        };
        memcpy(VSyscallPage, VSyscallCode, sizeof(VSyscallCode));
        mprotect(VSyscallPage, FEXCore::Utils::FEX_PAGE_SIZE, PROT_READ);
        VSyscallEntry = reinterpret_cast<uint64_t>(VSyscallPage);
      }

      AuxVariables.emplace_back(auxv_t {32, VSyscallEntry}); // AT_SYSINFO - Entry point to syscall
    }

    if (VDSOBase) {
      AuxVariables.emplace_back(auxv_t {33, reinterpret_cast<uint64_t>(VDSOBase)}); // AT_SYSINFO_EHDR - Address of the start of VDSO
    }

    AuxVariables.emplace_back(auxv_t {3, MainElfBase + MainElf.ehdr.e_phoff}); // Program header
    AuxVariables.emplace_back(auxv_t {7, InterpeterElfBase});                  // AT_BASE - Interpreter address
    AuxVariables.emplace_back(auxv_t {9, MainElfEntrypoint});                  // AT_ENTRY

    AuxVariables.emplace_back(auxv_t {0, 0}); // Null ender

    SetupStack();

    // Cleanup FDs so they don't stay open
    MainElf.Closefd();
    InterpElf.Closefd();
    return true;
  }

  // Helper for stack setup
  template<typename PointerType, typename AuxType, size_t PointerSize>
  static void SetupPointers(uintptr_t StackPointer, uint64_t AuxVOffset, uint64_t ArgumentOffset, uint64_t EnvpOffset,
                            const fextl::vector<fextl::string>& Args, const fextl::vector<fextl::string>& EnvironmentVariables,
                            const fextl::list<auxv_t>& AuxVariables, uint64_t* AuxTabBase, uint64_t* AuxTabSize) {
    // Pointer list offsets
    PointerType* ArgumentPointers = reinterpret_cast<PointerType*>(StackPointer + PointerSize);
    PointerType* PadPointers = reinterpret_cast<PointerType*>(StackPointer + PointerSize + Args.size() * PointerSize);
    PointerType* EnvpPointers = reinterpret_cast<PointerType*>(StackPointer + PointerSize + Args.size() * PointerSize + PointerSize);
    AuxType* AuxVPointers = reinterpret_cast<AuxType*>(StackPointer + AuxVOffset);

    // Arguments memory lives after everything else
    uint8_t* ArgumentBackingBase = reinterpret_cast<uint8_t*>(StackPointer + ArgumentOffset);
    uint8_t* EnvpBackingBase = reinterpret_cast<uint8_t*>(StackPointer + EnvpOffset);
    PointerType ArgumentBackingBaseGuest = StackPointer + ArgumentOffset;
    PointerType EnvpBackingBaseGuest = StackPointer + EnvpOffset;

    *reinterpret_cast<PointerType*>(StackPointer + 0) = Args.size();
    PadPointers[0] = 0;

    // If we don't have any, just make sure the first is nullptr
    EnvpPointers[0] = 0;

    uint64_t CurrentOffset = 0;
    for (size_t i = 0; i < Args.size(); ++i) {
      size_t ArgSize = Args[i].size();
      // Set the pointer to this argument
      ArgumentPointers[i] = ArgumentBackingBaseGuest + CurrentOffset;
      if (ArgSize > 0) {
        // Copy the string in to the final location
        memcpy(reinterpret_cast<void*>(ArgumentBackingBase + CurrentOffset), Args[i].data(), ArgSize);
      }

      // Set the null terminator for the string
      *reinterpret_cast<uint8_t*>(ArgumentBackingBase + CurrentOffset + ArgSize) = 0;

      CurrentOffset += ArgSize + 1;
    }

    CurrentOffset = 0;
    for (size_t i = 0; i < EnvironmentVariables.size(); ++i) {
      size_t EnvpSize = EnvironmentVariables[i].size();
      // Set the pointer to this argument
      EnvpPointers[i] = EnvpBackingBaseGuest + CurrentOffset;

      // Copy the string in to the final location
      if (EnvpSize) {
        memcpy(reinterpret_cast<void*>(EnvpBackingBase + CurrentOffset), EnvironmentVariables[i].data(), EnvpSize);
      }

      // Set the null terminator for the string
      *reinterpret_cast<uint8_t*>(EnvpBackingBase + CurrentOffset + EnvpSize) = 0;

      CurrentOffset += EnvpSize + 1;
    }

    // Last envp needs to be nullptr
    EnvpPointers[EnvironmentVariables.size()] = 0;

    for (size_t i = 0; const auto& Variable : AuxVariables) {
      AuxVPointers[i].key = Variable.key;
      AuxVPointers[i].val = Variable.val;
      ++i;
    }

    *AuxTabBase = reinterpret_cast<uint64_t>(AuxVPointers);
    *AuxTabSize = sizeof(AuxType) * AuxVariables.size();
  }

  // Get the current memory map from /proc/self/stat
  static bool GetCurrentMap(struct prctl_mm_map& map) {

    // /proc/self/stat has 52 fields of at most 20 digits each (UINT64_MAX).
    // 52*20 = 1040, so 2048 is a conservative upper bound
    char stat_buffer[2048];
    ssize_t bytes_read = FEXCore::FileLoading::LoadFileToBuffer("/proc/self/stat", stat_buffer);

    // Ensure we don't read past the end into garbage data
    stat_buffer[std::clamp(bytes_read, 0L, static_cast<ssize_t>(sizeof(stat_buffer)) - 1)] = '\0';

    // See man proc_pid_stat
    int items_read = sscanf(stat_buffer,
                            "%*d %*s %*c %*d %*d "      // 1 to 5
                            "%*d %*d %*d %*u %*u "      // 6 to 10
                            "%*u %*u %*u %*u %*u "      // 11 to 15
                            "%*d %*d %*d %*d %*d "      // 16 to 20
                            "%*d %*u %*u %*d %*u "      // 21 to 25
                            "%llu %llu %llu %*u %*u "   // 26 to 30
                            "%*u %*u %*u %*u %*u "      // 31 to 35
                            "%*u %*u %*d %*d %*u "      // 36 to 40
                            "%*u %*u %*u %*d %llu "     // 40 to 45
                            "%llu %llu %llu %llu %llu " // 46 to 50
                            "%llu",                     // 51
                            &map.start_code, &map.end_code, &map.start_stack, &map.start_data, &map.end_data, &map.start_brk,
                            &map.arg_start, &map.arg_end, &map.env_start, &map.env_end);

    if (items_read != 10) {
      return false;
    }

    map.brk = reinterpret_cast<uint64_t>(sbrk(0));

    // The kernel will leave these values unchanged, see implementation in sys.c
    map.auxv = NULL;
    map.auxv_size = 0;
    map.exe_fd = -1;

    return true;
  }

  // Point the OS to our new stack's argument data
  void RemapArgumentData(uintptr_t NewArgStart, uint64_t ArgSize) {
    struct prctl_mm_map map {};
    if (GetCurrentMap(map)) {
      map.arg_start = NewArgStart;
      map.arg_end = NewArgStart + ArgSize;

      int r = prctl(PR_SET_MM, PR_SET_MM_MAP, &map, sizeof(map), 0L);
      if (r != 0) {
        LogMan::Msg::EFmt("Failed to remap /proc/pid/cmdline data (prctl failed: result {}, errno {})", r, errno);
      }
    } else {
      LogMan::Msg::EFmt("Failed to remap /proc/pid/cmdline data (GetCurrentMap failed)");
    }
  }

  // Setups the stack initial data (argv, envp, auxv)
  void SetupStack() {
    StackPointer += StackSize();
    // Set up our initial CPU state
    uint64_t SizeOfPointer = Is64BitMode() ? 8 : 4;

    uint64_t TotalArgumentMemSize {};

    TotalArgumentMemSize += SizeOfPointer;                               // Argument counter size
    TotalArgumentMemSize += SizeOfPointer * ApplicationArgs.size();      // Pointers to strings
    TotalArgumentMemSize += SizeOfPointer;                               // Padding for something
    TotalArgumentMemSize += SizeOfPointer * EnvironmentVariables.size(); // Argument location for envp
    TotalArgumentMemSize += SizeOfPointer;                               // envp nullptr ender

    uint64_t AuxVOffset = TotalArgumentMemSize;
    if (SizeOfPointer == 8) {
      TotalArgumentMemSize += sizeof(auxv_t) * AuxVariables.size();
    } else {
      TotalArgumentMemSize += sizeof(auxv32_t) * AuxVariables.size();
    }

    ArgumentOffset = TotalArgumentMemSize;
    TotalArgumentMemSize += ArgumentBackingSize;

    uint64_t EnvpOffset = TotalArgumentMemSize;
    TotalArgumentMemSize += EnvironmentBackingSize;

    uint64_t PlatformNameLocation = TotalArgumentMemSize;
    TotalArgumentMemSize += platform_string_max_size;

    uint64_t ExecFNLocation = TotalArgumentMemSize;
    TotalArgumentMemSize += ApplicationArgs[0].size() + 1;

    // Align the argument block to 16 bytes to keep the stack aligned
    TotalArgumentMemSize = FEXCore::AlignUp(TotalArgumentMemSize, 16);

    // Random number location
    uint64_t RandomNumberLocation = TotalArgumentMemSize;
    TotalArgumentMemSize += 16;

    // Offset the stack by how much memory we need
    StackPointer -= TotalArgumentMemSize;

    // Setup our AUXP values that need memory now that the stack is setup
    AuxPlatform->val = StackPointer + PlatformNameLocation;
    char* PlatformLoc = reinterpret_cast<char*>(AuxPlatform->val);
    memset(PlatformLoc, 0, platform_string_max_size);
    if (Is64BitMode()) {
      strncpy(PlatformLoc, platform_name_x86_64.data(), platform_string_max_size);
    } else {
      strncpy(PlatformLoc, platform_name_i686.data(), platform_string_max_size);
    }

    // Random value is always 128bits
    AuxRandom->val = StackPointer + RandomNumberLocation;
    uint64_t* RandomLoc = reinterpret_cast<uint64_t*>(AuxRandom->val);
    uint64_t* HostRandom = reinterpret_cast<uint64_t*>(getauxval(AT_RANDOM));
    if (HostRandom) {
      // Pass through the host's random values
      RandomLoc[0] = HostRandom[0];
      RandomLoc[1] = HostRandom[1];
    } else {
      // Nothing provided from the kernel, generate our own random values.
      if (!GetRandom(&RandomLoc[0], sizeof(uint64_t) * 2)) {
        // getrandom failed for some reason.
        RandomLoc[0] = 0;
        RandomLoc[1] = 0;
        LogMan::Msg::EFmt("RNG failed. AT_RANDOM will not be random.");
      }
    }

    // Setup ExecFN aux
    AuxExecFN->val = StackPointer + ExecFNLocation;
    strncpy(reinterpret_cast<char*>(AuxExecFN->val), ApplicationArgs[0].c_str(), ApplicationArgs[0].size() + 1);

    // Stack setup
    // [0, 8):   Argument Count
    // [8, 16):  Argument Pointer 0
    // [16, 24): Argument Pointer 1
    // ....
    // [Pad1, +8): Some Pointer
    // [envp, +8): envp pointer
    // [Pad2End, +8): Argument String 0
    // [+8, +8): String 1
    // ...
    // [argvend, +8): envp[0]
    // ...
    // [envpend, +8): nullptr

    if (SizeOfPointer == 8) {
      SetupPointers<uint64_t, auxv_t, 8>(StackPointer, AuxVOffset, ArgumentOffset, EnvpOffset, ApplicationArgs, EnvironmentVariables,
                                         AuxVariables, &AuxTabBase, &AuxTabSize);
    } else {
      SetupPointers<uint32_t, auxv32_t, 4>(StackPointer, AuxVOffset, ArgumentOffset, EnvpOffset, ApplicationArgs, EnvironmentVariables,
                                           AuxVariables, &AuxTabBase, &AuxTabSize);
    }

    RemapArgumentData(StackPointer + ArgumentOffset, ArgumentBackingSize);
  }

  fextl::vector<const char*> GetExecveArguments() const override {
    return LoaderArgs;
  }

  AuxvResult GetAuxv() const override {
    return {
      .address = AuxTabBase,
      .size = AuxTabSize,
    };
  }

  uint64_t GetBaseOffset() const override {
    return BaseOffset;
  }

  uint64_t GetMainElfBase() const {
    return MainElfBase;
  }

  bool Is64BitMode() const {
    return MainElf.type == ::ELFLoader::ELFContainer::TYPE_X86_64;
  }

  ::ELFLoader::ELFContainer::BRKInfo GetBRKInfo() {
    return ::ELFLoader::ELFContainer::BRKInfo {BrkStart, BRK_SIZE};
  }

  bool ELFWasLoaded() {
    return ElfValid;
  }

  void SetVDSOBase(void* Base) {
    VDSOBase = Base;
  }

  void CalculateHWCaps(FEXCore::Context::Context* ctx) {
    // HWCAP is just CPUID function 0x1, the EDX result
    auto res_1 = ctx->RunCPUIDFunction(1, 0);
    auto res_7 = ctx->RunCPUIDFunction(7, 0);

    HWCap = res_1.edx;

    // HWCAP2 is as follows:
    // Bits:
    // 0 - MONITOR/MWAIT available in CPL3
    // 1 - FSGSBASE instructions available in CPL3
    HWCap2 = (res_7.ebx & 1) ? (1U << 1) : 0; // FSGSBase is exposed if CPUID_7_ebx[0] is set.

    // We need to know if we support AVX for AT_MINSIGSTKSZ
    SupportsAVX = !!(res_1.ecx & (1U << 28));
  }

  uint64_t CalculateSignalStackSize() const {
    // We must calculate the required signal stack size that the "kernel" consumes.
    // For FEX this means the amount of state we store in to the guest stack, not including the amount
    // that FEX stores in to the host stack as well.
    //
    // This needs to match what we do in FEXCore's dispatcher (Which should at some point be moved to the frontend).
    //
    // This roughly means that we need to calculate the combined size of:
    // - xstate or _libc_fstate depending on AVX support
    // - ucontext_t
    // - siginfo_t
    // Size of state requiring to be stored is different between 32-bit and 64-bit.

    uint64_t Result {};
    if (Is64BitMode()) {
      Result += sizeof(FEXCore::x86_64::ucontext_t);
      Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86_64::ucontext_t));
      if (SupportsAVX) {
        Result += sizeof(FEXCore::x86_64::xstate);
        Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86_64::xstate));
      } else {
        Result += sizeof(FEXCore::x86_64::_libc_fpstate);
        Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86_64::_libc_fpstate));
      }

      Result += sizeof(siginfo_t);
      Result = FEXCore::AlignUp(Result, alignof(siginfo_t));
    } else {
      Result += sizeof(FEXCore::x86::ucontext_t);
      Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86::ucontext_t));
      if (SupportsAVX) {
        Result += sizeof(FEXCore::x86::xstate);
        Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86::xstate));
      } else {
        Result += sizeof(FEXCore::x86::_libc_fpstate);
        Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86::_libc_fpstate));
      }

      Result += sizeof(FEXCore::x86::siginfo_t);
      Result = FEXCore::AlignUp(Result, alignof(FEXCore::x86::siginfo_t));
    }

    return Result;
  }

  constexpr static uint64_t BRK_SIZE = 8 * 1024 * 1024;
  constexpr static uint64_t STACK_SIZE = 8 * 1024 * 1024;
  constexpr static uint64_t FULL_STACK_SIZE = 128 * 1024 * 1024;
  constexpr static uint64_t STACK_HINT_32 = 0xFFFFE000 - FULL_STACK_SIZE;
  constexpr static uint64_t STACK_HINT_64 = 0x7FFFFFFFF000 - FULL_STACK_SIZE;

  fextl::vector<fextl::string> EnvironmentVariables;
  fextl::vector<const char*> LoaderArgs;

  fextl::list<auxv_t> AuxVariables;
  uint64_t AuxTabBase {}, AuxTabSize {};
  uint64_t ArgumentBackingSize {};
  uint64_t ArgumentOffset {};
  uint64_t EnvironmentBackingSize {};
  uint64_t BaseOffset {};
  void* VDSOBase {};
  uint64_t HWCap {};
  uint64_t HWCap2 {};
  bool SupportsAVX {};

  auxv_t* AuxRandom {};
  auxv_t* AuxPlatform {};
  auxv_t* AuxExecFN {};

  static constexpr std::string_view platform_name_x86_64 = "x86_64";
  static constexpr std::string_view platform_name_i686 = "i686";
  // Need to include null character.
  static constexpr size_t platform_string_max_size = std::max(platform_name_x86_64.size(), platform_name_i686.size()) + 1;

  FEX_CONFIG_OPT(AdditionalArguments, ADDITIONALARGUMENTS);
  FEX_CONFIG_OPT(InjectLibSegFault, INJECTLIBSEGFAULT);
};
